package han.chensing.latos.common.level.content.timeLine.valuebook;

import han.chensing.latos.common.level.content.timeLine.TimeLineEventSet;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineEvent;
import han.chensing.latos.common.level.content.timeLine.events.TimeLineMotionEvent;
import han.chensing.latos.common.level.content.timeLine.frame.FrameGeneral;
import han.chensing.latos.common.level.content.timeLine.motion.ValueMotion;
import han.chensing.latos.common.level.content.timeLine.motion.interpolator.InstantInterpolator;
import han.chensing.latos.common.level.util.MathUtil;

import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class ValueBook implements Serializable {
    @Serial
    private static final long serialVersionUID = -3599291173133973939L;

    private final List<ValueBookPage> pages=new ArrayList<>();
    private int cachedPagePosition=0;

    public void initContainer(TimeLineEventSet<TimeLineMotionEvent,FrameGeneral> timeLineEventSet){
        List<FrameGeneral> keyFrames = timeLineEventSet.getKeyFrames();
        List<TimeLineMotionEvent> timeLineEvents = timeLineEventSet.getTimeLineEvents();
        ValueBookPage page=new ValueBookPage();
        pages.clear();
        page.setFromTime(0);
        float maxTime1;
        float maxTime2;
        maxTime1= timeLineEvents.isEmpty() ?0:timeLineEvents.get(timeLineEvents.size()-1).getToTime();
        maxTime2= keyFrames.isEmpty() ?0:keyFrames.get(keyFrames.size()-1).getTime();
        page.setToTime(Math.max(maxTime1,maxTime2));
        pages.add(page);
        for(int keyFramesIndex=0,timeLineEventsIndex=0;
            keyFramesIndex<keyFrames.size()||timeLineEventsIndex<timeLineEvents.size();){
            boolean hasKeyFrame=MathUtil.isInRange(0,keyFramesIndex,keyFrames.size()-1);
            boolean hasTimeLineEvent=MathUtil.isInRange(0,timeLineEventsIndex,timeLineEvents.size()-1);
            FrameGeneral frame = hasKeyFrame ? keyFrames.get(keyFramesIndex) : null;
            TimeLineEvent<?> timeLineEvent = hasTimeLineEvent ? timeLineEvents.get(timeLineEventsIndex) : null;
            if (hasKeyFrame&&hasTimeLineEvent){
                float frameTime = frame.getTime();
                float timeLineEventTime = timeLineEvent.getFromTime();
                if (timeLineEventTime<=frameTime){
                    addEventEntry(page,timeLineEvent);
                    timeLineEventsIndex++;
                }else{
                    addFrameEntry(page,frame);
                    keyFramesIndex++;
                }
            }else {
                if (hasKeyFrame) {
                    addFrameEntry(page,frame);
                    keyFramesIndex++;
                    continue;
                }else if (hasTimeLineEvent) {
                    addEventEntry(page,timeLineEvent);
                    timeLineEventsIndex++;
                    continue;
                }
                break;
            }
        }
        page.connectEntries();
    }

    public float getValue(float absoluteTime){
        ValueBookPage currentPage = getCurrentPage(absoluteTime);
        return currentPage.getValue(absoluteTime - currentPage.getFromTime());
    }

    private void addFrameEntry(ValueBookPage page, FrameGeneral frame){
        ValueBookEntry valueBookEntry=new ValueBookEntry();
        valueBookEntry.setRelativeFromTime(frame.getTime());
        valueBookEntry.setInterpolator(new InstantInterpolator());
        //FIXME Just for test
        valueBookEntry.setFromValue(frame.getBpm());
        valueBookEntry.setToValue(frame.getBpm());
        page.getEntries().add(valueBookEntry);
    }

    private void addEventEntry(ValueBookPage page,TimeLineEvent<?> timeLineEvent){
        ValueBookEntry valueBookEntry=new ValueBookEntry();
        valueBookEntry.setRelativeFromTime(timeLineEvent.getFromTime());
        if (timeLineEvent instanceof TimeLineMotionEvent timeLineLineEvent) {
            ValueMotion valueMotion = timeLineLineEvent.getValueMotion();
            valueBookEntry.setFromValue(valueMotion.getStartValue());
            valueBookEntry.setToValue(valueMotion.getEndValue());
            valueBookEntry.setInterpolator(valueMotion.getInterpolator());
        }
        page.getEntries().add(valueBookEntry);
    }

    private ValueBookPage getCurrentPage(float absoluteTime) {
        while(true) {
            if (!MathUtil.isInRange(0,cachedPagePosition,pages.size()-1)){
                cachedPagePosition= (int) MathUtil.range(0,cachedPagePosition,pages.size()-1);
                return pages.get(cachedPagePosition);
            }
            ValueBookPage valueBookPage = pages.get(cachedPagePosition);
            float relativeFromTime = valueBookPage.getFromTime();
            float relativeToTime = valueBookPage.getToTime();
            int range = MathUtil.judgeRange(relativeFromTime, absoluteTime, relativeToTime);
            if (range==0){
                return valueBookPage;
            }else if (range==-1){
                cachedPagePosition++;
            }else if (range==1){
                cachedPagePosition--;
            }
        }
    }
}
